package com.showsu.editor.utils;


import cn.hutool.core.util.ReUtil;
import cn.hutool.http.HtmlUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Http请求工具类
 *
 * @author eollse
 */
@Slf4j
public class HttpClientUtil {

    private final int CONNECTION_TIME_OUT = 3000;
    private final int REQUEST_TIME_OUT = 5000;
    private final int SOCKET_TIME_OUT = 5000;

    private final static int OK_CODE = 200;

    public RequestConfig setConfig() {
        //设置请求超时
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectTimeout(CONNECTION_TIME_OUT).setConnectionRequestTimeout(REQUEST_TIME_OUT)
                .setSocketTimeout(SOCKET_TIME_OUT).build();
        return requestConfig;
    }

    /**
     * 处理get请求
     * @param url 请求路径
     * @param encoding
     * @return
     */
    public static String get(String url, String encoding) {
        return get(url, encoding, null, null, null);
    }

    /**
     * 处理get请求
     * @param url 请求路径
     * @param encoding
     * @param headers 请求头
     * @param proxyIp 代理ip
     * @param proxyPort 代理端口
     * @return
     */
    public static String get(String url, String encoding, Map<String, String> headers, String proxyIp, Integer proxyPort) {
        if (proxyPort != null && proxyPort > 0) {
            url = "http://" + proxyIp + ":" +proxyPort+"/" + url.substring(url.indexOf("//"));
        }
        //请求结果
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        String content = "";
        try {
            httpClient = HttpClients.createDefault();
//            实例化get方法
            HttpGet httpget = new HttpGet(url);
            if (headers != null) {
                Set<String> headerKeys = headers.keySet();
                for (String headerKey : headerKeys) {
                    httpget.setHeader(headerKey, headers.get(headerKey));
                }
            }
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(Constant.defaultTimeOut).setConnectionRequestTimeout(Constant.defaultTimeOut)
                    .setSocketTimeout(Constant.defaultTimeOut).build();
            httpget.setConfig(requestConfig);
            //执行get方法
            response = httpClient.execute(httpget);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
            log.error("连接或读取超时：" + e.toString());
        } catch (ConnectTimeoutException e) {
            e.printStackTrace();
            log.error("连接超时：" + e.toString());
        } catch (NoHttpResponseException e) {
            e.printStackTrace();
            log.error("没有响应：" + e.toString());
        } catch (UnknownHostException e) {
            e.printStackTrace();
            log.error("找不到服务器：" + e.toString());
        } catch (HttpHostConnectException e) {
            e.printStackTrace();
            log.error("连接超时：" + e.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    log.error("response流关闭异常：", e.getMessage());
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.error("httpClient流关闭异常：", e.getMessage());
                }
            }
        }
        return content;
    }

    /**
     * 处理get请求
     * @param url 请求路径
     * @param encoding
     * @param headers 请求头
     * @param proxyIp 代理ip
     * @param proxyPort 代理端口
     * @return
     */
    public static String get(String url, String encoding, Map<String, String> headers, CookieStore cookieStore, String proxyIp, Integer proxyPort) {
        if (proxyPort != null && proxyPort > 0) {
            url = "http://" + proxyIp + ":" +proxyPort+"/" + url.substring(url.indexOf("//"));
        }
        //请求结果
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        String content = "";
        try {
            httpClient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
//            实例化get方法
            HttpGet httpget = new HttpGet(url);
            if (headers != null) {
                Set<String> headerKeys = headers.keySet();
                for (String headerKey : headerKeys) {
                    httpget.setHeader(headerKey, headers.get(headerKey));
                }
            }
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(Constant.defaultTimeOut).setConnectionRequestTimeout(Constant.defaultTimeOut)
                    .setSocketTimeout(Constant.defaultTimeOut).build();
            httpget.setConfig(requestConfig);
            //执行get方法
            response = httpClient.execute(httpget);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (SocketTimeoutException e) {
            e.printStackTrace();
            log.error("连接或读取超时：" + e.toString());
        } catch (ConnectTimeoutException e) {
            e.printStackTrace();
            log.error("连接超时：" + e.toString());
        } catch (NoHttpResponseException e) {
            e.printStackTrace();
            log.error("没有响应：" + e.toString());
        } catch (UnknownHostException e) {
            e.printStackTrace();
            log.error("找不到服务器：" + e.toString());
        } catch (HttpHostConnectException e) {
            e.printStackTrace();
            log.error("连接超时：" + e.toString());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭资源
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    log.error("response流关闭异常：", e.getMessage());
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.error("httpClient流关闭异常：", e.getMessage());
                }
            }
        }
        return content;
    }

    /**
     * 处理get请求
     * @param url 请求路径
     * @param encoding 编码
     * @param headers 请求头
     * @param timeout 超时时长
     * @return
     */
    public static String get(String url, String encoding, Map<String, String> headers, Integer timeout) {
        //请求结果
        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = null;
        String content = "";
        try {
            httpClient = HttpClients.createDefault();
            HttpGet httpget = new HttpGet(url);
            if (headers != null) {
                Set<String> headerKeys = headers.keySet();
                for (String headerKey : headerKeys) {
                    httpget.setHeader(headerKey, headers.get(headerKey));
                }
            }
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(timeout).setConnectionRequestTimeout(timeout)
                    .setSocketTimeout(timeout).build();
            httpget.setConfig(requestConfig);
            //执行get方法
            response = httpClient.execute(httpget);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (Exception e) {
            log.error("com.showsu.editor.utils.HttpClientUtil.get(String,String,Map,String,Integer) error", e);
        } finally {
            // 关闭资源
            if (null != response) {
                try {
                    response.close();
                } catch (IOException e) {
                    log.error("response流关闭异常：", e);
                }
            }
            if (null != httpClient) {
                try {
                    httpClient.close();
                } catch (IOException e) {
                    log.error("httpClient流关闭异常：", e);
                }
            }
        }
        return content;
    }


    /**
     * 处理post请求.
     * @param url
     * @param encoding
     * @param headers
     * @param cookieStore
     * @param params
     * @param proxyIp
     * @param proxyPort
     * @param timeOut 超时时间
     * @return
     */
    public static String post(String url, String encoding, Map<String, String> headers,
                              CookieStore cookieStore, Map<String, String> params,
                              String proxyIp, Integer proxyPort, Integer timeOut) {
        if (StringUtils.isNotBlank(proxyIp) && proxyPort != null && proxyPort > 0) {
            url = "http://" + proxyIp + ":" +proxyPort+"/" + url.substring(url.indexOf("//"));
        }
//        CloseableHttpClient httpclient = HttpClients.createDefault();
        CloseableHttpClient httpclient = HttpClients.custom().setDefaultCookieStore(cookieStore).build();
        HttpPost httpPost = new HttpPost(url);
        if (headers != null) {
            Set<String> headerKeys = headers.keySet();
            for (String headerKey : headerKeys) {
                httpPost.setHeader(headerKey, headers.get(headerKey));
            }
        }
        //处理参数
        List<NameValuePair> nvps = new ArrayList<>();
        Set<String> keySet = params.keySet();
        for (String key : keySet) {
            nvps.add(new BasicNameValuePair(key, params.get(key)));
        }
        //结果  
        CloseableHttpResponse response;
        String content = "";
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(Constant.defaultTimeOut_20)
                    .setConnectionRequestTimeout(Constant.defaultTimeOut_20)
                    .setSocketTimeout(timeOut == null ? Constant.defaultTimeOut : timeOut).build();
            httpPost.setConfig(requestConfig);
            //提交的参数  
            UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(nvps, encoding);
            //将参数给post方法  
            httpPost.setEntity(uefEntity);
            //执行post方法  
            response = httpclient.execute(httpPost);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (Exception e) {
            log.error("HttpClientUtil.post(String,String,Map<String,String>,Map<String,String>,String,Integer) error", e);
        }
        return content;
    }

    /**
     * 处理post请求.
     * @param url
     * @param encoding
     * @param headers
     * @param params
     * @param proxyIp
     * @param proxyPort
     * @param timeOut 超时时间
     * @return
     */
    public static String post(String url, String encoding, Map<String, String> headers, Map<String, String> params, String proxyIp, Integer proxyPort, Integer timeOut) {
        if (StringUtils.isNotBlank(proxyIp) && proxyPort != null && proxyPort > 0) {
            url = "http://" + proxyIp + ":" +proxyPort+"/" + url.substring(url.indexOf("//"));
        }
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        if (headers != null) {
            Set<String> headerKeys = headers.keySet();
            for (String headerKey : headerKeys) {
                httpPost.setHeader(headerKey, headers.get(headerKey));
            }
        }
        //处理参数
        List<NameValuePair> nvps = new ArrayList<>();
        Set<String> keySet = params.keySet();
        for (String key : keySet) {
            nvps.add(new BasicNameValuePair(key, params.get(key)));
        }
        //结果
        CloseableHttpResponse response;
        String content = "";
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(Constant.defaultTimeOut_20).
                            setConnectionRequestTimeout(Constant.defaultTimeOut_20)
                    .setSocketTimeout(timeOut == null ? Constant.defaultTimeOut : timeOut).build();
            httpPost.setConfig(requestConfig);
            //提交的参数
            UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(nvps, encoding);
            //将参数给post方法
            httpPost.setEntity(uefEntity);
            //执行post方法
            response = httpclient.execute(httpPost);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                content = EntityUtils.toString(response.getEntity(), encoding);
            }
        } catch (Exception e) {
            log.error("HttpClientUtil.post(String,String,Map<String,String>,Map<String,String>,String,Integer) error", e);
        }
        return content;
    }

    /**
     * 处理post请求.
     *
     * @param url    请求路径
     * @param params 参数
     * @return json
     */
    public static String postByJson(String url, Map<String, String> headers, String params, Integer timeout) {
        return postByJson(url, headers, params, null, null, timeout);
    }

    /**
     * 处理post请求.
     *
     * @param url    请求路径
     * @param params 参数
     * @return json
     */
    public static String postByJson(String url, Map<String, String> headers, String params, String proxyIp, Integer proxyPort, Integer timeout) {
        if (StringUtils.isNotBlank(proxyIp) && proxyPort != null && proxyPort > 0) {
            url = "http://" + proxyIp + ":" +proxyPort+"/" + url.substring(url.indexOf("//"));
        }
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeader("Content-Type", "application/json");
        if (headers != null) {
            Set<String> headerKeys = headers.keySet();
            for (String headerKey : headerKeys) {
                httpPost.setHeader(headerKey, headers.get(headerKey));
            }
        }
        //结果
        CloseableHttpResponse response;
        String content = "";
        try {
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(timeout == null ? Constant.defaultTimeOut : timeout)
                    .setConnectionRequestTimeout(timeout == null ? Constant.defaultTimeOut : timeout)
                    .setSocketTimeout(timeout == null ? Constant.defaultTimeOut : timeout)
                    .build();
            httpPost.setConfig(requestConfig);
            httpPost.setEntity(new StringEntity(params, "utf-8"));
            //执行post方法
            response = httpclient.execute(httpPost);
            String result = resultBody(response);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                return result;
            } else {
                log.error("result-error:" + result);
            }
        } catch (Exception e) {
            log.error("HttpClientUtil.postByJson(String,Map<String, String>,String,String,Integer) error", e);
        }
        return content;
    }

    private static String resultBody(CloseableHttpResponse response) {
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        try {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = response.getEntity().getContent().read(buffer)) != -1) {
                result.write(buffer, 0, length);
            }
            return result.toString("utf-8");
        } catch (Exception e) {
            log.error("HttpClientUtil.resultBody(CloseableHttpResponse) error", e);
        } finally {
            try {
                result.close();
            } catch (IOException e) {
                log.error("HttpClientUtil.resultBody(CloseableHttpResponse) close result error", e);
            }
        }
        return null;
    }

    /**
     * 图片下载
     *
     * @param url
     * @return
     */
    public InputStream downPicture(String url) {
        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(setConfig());
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response;
        try {
            //执行get方法
            response = httpClient.execute(httpGet);
            if (response.getStatusLine().getStatusCode() == OK_CODE) {
                return response.getEntity().getContent();
            }

        } catch (ClientProtocolException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 针对谷歌浏览器80修改了SameSite属性进行兼容
     * @param request
     * @return
     */
    public static boolean shouldSendSameSiteNone(HttpServletRequest request) {
        try {
            String userAgent = request.getHeader("User-Agent");
            return !(hasWebKitSameSiteBug(userAgent) || dropsUnrecognizedSameSiteCookies(userAgent));
        } catch (Exception e) {
            log.error("检验用户浏览器agent出错", e);
        }
        return false;
    }

    public static String extractUrl(String text){
        String formatStr=HtmlUtil.unescape(text);
        return ReUtil.getGroup0("https?://([\\w\\.-]+)\\.([a-zA-Z\\.]{2,6})([\\/\\w\\.-]*)*\\/?(\\?[\\w\\&\\=\\.\\-]*)?",formatStr);
    }

    /**
     * 判断xhs链接是否302到包含有404的地址
     * 如：http://xhslink.com/dkNDBG，会跳转到：https://www.xiaohongshu.com/404?source=note&noteId=660a8f2b000000001b00fd70&app_platform=android&ignoreEngage=true&app_version=8.31.0&share_from_user_hidden=true&type=normal&author_share=1&xhsshare=CopyLink&shareRedId=ODY1ODk6Sj82NzUyOTgwNjY0OThJSjhP&apptime=1713248312
     * @return
     */
    public static Boolean getXhsLinkRedirectUrlIs404(String url) {
        Boolean result = false;
        try {
            url=extractUrl(url);
            URL obj = new URL(url);
            HttpURLConnection conn = (HttpURLConnection) obj.openConnection();
            try {
                conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36");
                //关闭自动重定向
                conn.setInstanceFollowRedirects(false);
                // 设置超时时间
                conn.setConnectTimeout(1000);
                int responseCode = conn.getResponseCode();
                if ((responseCode == HttpURLConnection.HTTP_MOVED_TEMP
                        || responseCode == HttpURLConnection.HTTP_MOVED_PERM
                        || responseCode == HttpURLConnection.HTTP_SEE_OTHER)) {
                    // 获取header中的重定向新链接
                    String newUrl = conn.getHeaderField("Location");
                    if (StringUtils.isNotBlank(newUrl) && newUrl.indexOf("404?") > -1) {
                        result = true;
                    }
                }
            }catch (Exception ignored){}
            finally {
                conn.disconnect();
            }
        } catch (Exception e) {
            log.error("getXhsLinkRedirectUrlIs404(String) error", e);
        }
        return result;
    }

    private static boolean dropsUnrecognizedSameSiteCookies(String userAgent) {
        if (isUcBrowser(userAgent)) {
            return !isUcBrowserVersionAtLeast(12, 13, 2, userAgent);

        }
        return isChromiumBased(userAgent) &&
                isChromiumVersionAtLeast(51, userAgent) &&
                !isChromiumVersionAtLeast(67, userAgent);
    }

    private static boolean hasWebKitSameSiteBug(String userAgent) {
        boolean b = isIosVersion("12", userAgent) ||
                (isMacosxVersion("10", 14, userAgent) &&
                        (isSafari(userAgent) || isMacEmbeddedBrowser(userAgent)));
        return b;
    }

    private static boolean isIosVersion(String major, String useragent) {
        String regex = "\\(iP.+; CPU .*OS (\\d+)[_\\d]*.*\\) AppleWebKit\\/";
        // Extract digits from first capturing group.
        Matcher matcher = Pattern.compile(regex).matcher(useragent);
        while (matcher.find()) {
            return major.equals(matcher.group(1));
        }
        return false;
    }

    private static boolean isMacosxVersion(String major, Integer minor, String useragent) {
        String regex = "\\(Macintosh;.*Mac OS X (\\d+)_(\\d+)[_\\d]*.*\\) AppleWebKit\\/";
        // Extract digits from first and second capturing groups.
        Matcher matcher = Pattern.compile(regex).matcher(useragent);
        String first = null;
        Integer second = null;

        while (matcher.find()) {
            first = matcher.group(1);
            second = Integer.parseInt(matcher.group(2));
        }
        return first != null && second != null && major.equals(first);
//        return first != null && second != null && major.equals(first) && second >= minor;

    }

    private static boolean isSafari(String useragent) {
        String safari_regex = "Version\\/.* Safari\\/";
        return Pattern.compile(safari_regex).matcher(useragent).find() && !isChromiumBased(useragent);
    }

    private static boolean isMacEmbeddedBrowser(String useragent) {
        String regex = "^Mozilla\\/[\\.\\d]+ \\(Macintosh;.*Mac OS X [_\\d]+\\) AppleWebKit\\/[\\.\\d]+ \\(KHTML, like Gecko\\)$";
        return Pattern.compile(regex).matcher(useragent).find();
    }

    private static boolean isChromiumBased(String useragent) {
        String regex = "Chrom(e|ium)";
        return Pattern.compile(regex).matcher(useragent).find();
    }

    private static boolean isChromiumVersionAtLeast(int major, String useragent) {
        String regex = "Chrom[^ \\/]+\\/(\\d+)[\\.\\d]* ";
        // Extract digits from first capturing group.
        Matcher matcher = Pattern.compile(regex).matcher(useragent);
        Integer version = null;
        while (matcher.find()) {
            version = Integer.parseInt(matcher.group(1));
        }
        return version != null && version >= major;
    }

    private static boolean isUcBrowser(String useragent) {
        String regex = "UCBrowser\\/";
        return Pattern.compile(regex).matcher(useragent).find();
    }

    private static boolean isUcBrowserVersionAtLeast(int major, int minor, int build, String useragent) {
        String regex = "UCBrowser\\/(\\d+)\\.(\\d+)\\.(\\d+)[\\.\\d]* ";
        // Extract digits from three capturing groups.
        Matcher matcher = Pattern.compile(regex).matcher(useragent);
        List<String> result = new ArrayList<>();
        while (matcher.find()) {
            result.add(matcher.group());
        }
        if (result.size() < 3) {
            return false;
        }
        int major_version = Integer.parseInt(result.get(0));
        int minor_version = Integer.parseInt(result.get(1));
        int build_version = Integer.parseInt(result.get(2));
        if (major_version != major)
            return major_version > major;
        if (minor_version != minor)
            return minor_version > minor;
        return build_version >= build;
    }
}